<?php
/**
 * App端 购物车处理
 * Author: lyun
 * Date: 2018/11/21
 * Created by Panxsoft.
 */

namespace App\Http\Services\Api\V1\App\Cart;

use App\Models\Cart\Cart;
use App\Models\Currency;
use App\Models\Options\OptionDescription;
use App\Models\Options\OptionValueDescription;
use App\Models\Products\Product;
use App\Models\Products\ProductDiscount;
use App\Models\Products\ProductOption;
use App\Models\Products\ProductOptionValue;
use App\Models\Products\WeightClass;
use App\Services\Customers\AppCustomer;
use App\Services\Locale\Language;
use App\Services\MobileApp\AppEnv;
use App\Services\Utils\ApiChecker;
use App\Services\Utils\FormatPrice;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;

class ShoppingCartService
{
    use ApiChecker;

    /**
     * @param $customer_id
     *
     * @return Collection
     * @throws \Exception
     */
    public function getShoppingCartData($customer_id)
    {
        // 删除已经下架商品的购物车
        (new Cart)->doesntHave('product')->delete();

        /** @var Collection $carts */
        $carts = (new Cart)->where('customer_id', $customer_id)
            ->where('quantity', '>', Cart::QUANTITY_DEFAULT)
            ->with(
                'product.filterProductSpecialByLatest',
                'productDiscounts',
                'productSpecials',
                'productDescription'
            )
            ->latest('date_added')
            ->get();

        /** @var Currency $currency */
        $currency = AppEnv::currency();

        $this->transformCartProducts($carts, $currency);

        return $carts;
    }

    /**
     * @param $customer_id
     * @return int
     */
    public function getCartNum($customer_id)
    {
        // 删除已经下架商品的购物车
        (new Cart)->doesntHave('product')->delete();

        $cart_num = (new Cart)->where('customer_id', $customer_id)
            ->where('quantity', '>', Cart::QUANTITY_DEFAULT)
            ->count();

        return $cart_num;
    }

    /**
     * @param $cart_data
     * @return Cart
     * @throws \Exception
     */
    public function storeCartData(array $cart_data): Cart
    {
        $oldCart = $this->findOldSameCart($cart_data);
        if ($oldCart) {

            $this->checkCartQuantity($oldCart, $cart_data['quantity']);
            $oldCart->increment('quantity', $cart_data['quantity']);
            return $oldCart;
        } else {
            return (new Cart)->create($cart_data);
        }
    }

    /**
     * @param Cart $cart
     * @param $quantity
     *
     * @return bool
     * @throws \Exception
     */
    public function updateShoppingCartData(Cart $cart, $quantity)
    {
        // 获取商品的总库存数
        $quantity = $this->gainProductQuantity($cart->product_id, $quantity);

        $updated = $cart->update(['quantity'=>$quantity]);

        return $updated;
    }

    /**
     * @param array $option
     * @param Cart $cart
     * @param $product_id
     * @return bool
     * @throws \Exception
     */
    public function updateCartOption(array $option, Cart $cart, $product_id)
    {
//        $cart = $this->getCart($cart_id, $customer_id);

        $this->checkItemExist($cart->product_id === (int)$product_id);

        $updated = $cart->update(['option' => $option]);

        return $updated;
    }

    /**
     * @param array $cart_ids
     * @param $customer_id
     * @return bool|null
     * @throws \Exception
     */
    public function deleteShoppingCartData(array $cart_ids, $customer_id)
    {
        $deleted = (new Cart)->where('customer_id', $customer_id)
            ->whereIn('cart_id', $cart_ids)
            ->delete();

        return $deleted;
    }

    private function transformCartProducts(Collection $carts, Currency $currency)
    {
        $carts->transform(function(Cart $cart) use($currency) {
            /** @var Product $product */
            $product = $cart->product;
            $special_price  = $product->productSpecials->first()->price ?? null;
            $product_total_quantity = $product->quantity ?? Product::PRODUCT_QUANTITY_DEFAULT;

            $cart->name = $cart->productDescription->name ?? null;
            $cart->image = $product->origin_image ?? null;
            $cart->product_total_quantity = $product_total_quantity;   // TODO 单个sku总数只是主商品库存数量
            $currency_price = $currency->exchangePrice($product->calcSettlePrice($cart->quantity, $cart->option));

            // 单个商品数量时，实际价格。
            $actual_price = $currency->exchangePrice($product->calcSettlePrice(ProductDiscount::QUANTITY_DEFAULT, $cart->option));

            $cart->currency_settle_price = $currency_price;
            $cart->price = $actual_price;
            $cart->special_price = $special_price ? $currency->exchangePrice($special_price) : null;
            $cart->weight_by_kg = WeightClass::getWeightOption($product->weight, $product->weight_class_id);

            $cart->symbol_left = $currency->symbol_left;
            $cart->symbol_right = $currency->symbol_right;
            $cart->option_names = $this->getProductOption($cart->option);
            $cart->quantity = $this->getProductQuantity($product_total_quantity, $cart->quantity);
            $cart->product_minimum = $product->minimum ?? Product::PRODUCT_MINIMUM_QUANTITY;
            $product_discounts = $cart->productDiscounts;
            $product_special = $cart->productSpecials->first() ?? null;

            $cart->addHidden('product', 'productDescription', 'productDiscounts', 'productSpecials');

            $cart->product_discounts = $product_special ? [] : $product_discounts;

            return $cart;
        });
    }

    /**
     * @param array $option
     * @return array
     */
    private function getProductOption(array $option)
    {
        $new_option = [];

        foreach($option as $product_option_id => $product_option_value_id)
        {
            $option_name = (new ProductOption)->find($product_option_id)->optionDescription
                    ->name ?? null;
            if ( !$option_name ) return [];

            $product_option_value_ids = is_array($product_option_value_id) ? $product_option_value_id : [$product_option_value_id];

            $option_value_ids = ProductOptionValue::whereIn('product_option_value_id', $product_option_value_ids)
                ->pluck('option_value_id');

            $option_value_name = OptionValueDescription::where('language_id', AppEnv::languageId())
                ->whereIn('option_value_id', $option_value_ids)
                ->pluck('name');
            $option_value_names = $option_value_name->count() ? $option_value_name : $product_option_value_ids;

            $new_option[] = compact('option_name', 'option_value_names');
        }

        return $new_option;
    }

    /**
     * @param $product_total_quantity
     * @param $quantity
     * @return mixed
     */
    private function getProductQuantity($product_total_quantity, $quantity)
    {
        if ($product_total_quantity < $quantity) {
            $quantity = $product_total_quantity;
        }

        return $quantity;
    }


    /**
     * todo 购物车存在同一个用户有多个相同商品(选项不同)的记录
     *
     * @param array $cart_data
     * @return array
     */
    private function checkCartProduct(array $cart_data)
    {
        /** @var Cart $cart */
        $cart = Cart::where('customer_id', $cart_data['customer_id'])
            ->where('product_id', $cart_data['product_id'])
            ->first();

        $option = $cart->option ?? [];

        $new_option = $cart_data['option'];

        $count = count($new_option);
        $is_true = false;
        $num  = 0;

        foreach($new_option as $new_index => $new_value)
        {
            $num++;
            $value = $option[$new_index] ?? null;

            if (is_numeric($value) && ($value !== $new_value)) {
                break;
            } else if (is_array($value) && is_array($new_value)) {
                sort($value);
                sort($new_value);
                if (serialize($value) !== serialize($new_value)) break;
            } else {
                if (is_null($value)) break;
            }

            if ($num === $count) {
                $is_true = true;
            }
        }

        return compact('is_true', 'cart');
    }

    /**
     * 遍历检查购物车中是否存在相同的商品(选项)
     *
     * @param array $cart_data
     *
     * @return Cart|null
     */
    private function findOldSameCart(array $cart_data): ?Cart
    {
        $old_carts = (new Cart)
            // in case no customer login in future
            ->when($cart_data['customer_id'] === Cart::CUSTOMER_ID_DEFAULT,
                function (Builder $builder) use ($cart_data) {
                    $builder->where('session_id', $cart_data['session_id']);
                })
            ->where('customer_id', $cart_data['customer_id'])
            ->where('product_id', $cart_data['product_id'])
            ->get();

        return $old_carts->first(function (Cart $cart) use ($cart_data) {
            /**
             * @link http://php.net/manual/en/language.operators.array.php
             * laravel辅助函数
             */
            return array_sort_recursive($cart->option ?? []) === array_sort_recursive($cart_data['option']);
        });

    }

    /**
     * @param $product_id
     * @param $quantity
     *
     * @return int|mixed
     * @throws \App\Exceptions\ApiAbstractException
     */
    private function gainProductQuantity($product_id, $quantity)
    {
        $product = Product::memoFind($product_id);
            $this->checkItemExist($product, trans('cart.product'));

        $product_stock = $product->quantity ?: Product::PRODUCT_QUANTITY_DEFAULT;
        $product_minimum = $product->minimum ?? Product::PRODUCT_MINIMUM_QUANTITY;

        $this->checkUnless(($product_minimum > $quantity), trans('cart.quantity_minimum'));
        $this->checkUnless(($product_stock < $quantity), trans('cart.quantity_stock'));

        return $quantity;
    }

    /**
     * @param $cart_id
     * @param $customer_id
     * @return Cart|\Illuminate\Database\Eloquent\Model|null|object
     * @throws \Exception
     */
    private function getCart($cart_id, $customer_id)
    {
        $cart = (new Cart)
            ->where('cart_id', $cart_id)
            ->where('customer_id', $customer_id)
            ->first();
        $this->checkItemExist($cart);

        return $cart;
    }

    /**
     * @param Cart $oldCart
     * @param $quantity
     * @throws \Exception
     */
    private function checkCartQuantity(Cart $oldCart, $quantity)
    {
        $product = Product::memoFind($oldCart->product_id);

        $product_total_quantity = $product->quantity;
        $cart_quantity = $quantity + $oldCart->quantity;

        $this->checkIsTrue($cart_quantity <= $product_total_quantity, trans('cart.quantity_stock'));
    }
}